using System;
using System.Collections;
using Hanoi.Utils;

namespace Hanoi
{
    /// <summary>
    /// The three towers.
    /// </summary>
    public class HanoiGame
    {
        #region Class members and properties
        private FixedSizeStack[] _towers;
        public FixedSizeStack[] Towers
        {
            get { return _towers; }
        }

        private int _size;
        public int Size
        {
            get { return _size; }
        }

        private int _start;
        public int Start
        {
            get { return _start; }
        }

        private int _end;
        public int End
        {
            get { return _end; }
        }

        private int _moves;
        public int Moves
        {
            get { return _moves; }
            set { _moves = value; }
        }

        private bool _canUndo = false;

        public bool CanUndo
        {
            get { return _canUndo && !IsFinished(); }
        }

        private DateTime _startTime;
        public DateTime StartTime
        {
            get { return _startTime; }
        }

        private DateTime _endTime;
        public DateTime EndTime
        {
            get { return _endTime; }
        }

        private IGame _rules = null;

        public bool TimeControl
        {
            get { return _rules.TimeControled; }
        }
        public TimeSpan TimeAmount
        {
            get { return _rules.TimeAmount; }
        }

        private int _undoFrom = -1;
        private int _undoTo = -1;
        #endregion

        #region Initialization
        public HanoiGame(int size, int start, int end)
        {
            if (size <= 0)
                throw new NotSupportedException("Error: it must be one disk at least!");
            if (start == end)
                throw new NotSupportedException("Error: they are the same towers!");

            InitializeMembers(size, start, end);
        }

        public HanoiGame(IGame rules)
        {
            int start, end, size;
            start = rules.Start;
            end = rules.End;

            size = rules.Size;

            if (size <= 0)
                throw new NotSupportedException("Error: it must be one disk at least!");
            if (start == end)
                throw new NotSupportedException("Error: they are the same towers!");

            InitializeMembers(start, end, size, rules.Moves);

            _rules = rules;
        }


        private void InitializeMembers(int size, int start, int end)
        {
            _towers = new FixedSizeStack[3] { new FixedSizeStack(size),
                new FixedSizeStack(size), new FixedSizeStack(size) };
            _size = size;
            _start = start;
            _end = end;
            _moves = Convert.ToInt32(Math.Pow(2, size)) - 1;

            _startTime = DateTime.Now;

            FillStartTower();
        }

        private void InitializeMembers(int startColumn, int endColumn, int size, int moves)
        {
            InitializeMembers(size, startColumn, endColumn);
            if (moves > 0)
                _moves = moves;
        }

        private void FillStartTower()
        {
            for (int i = _size; i > 0; i--)
                _towers[_start].Push(
                    new Disk
                    (
                        i,
                        new System.Drawing.Point(_start, _size - i)
                    )
                );
        }
        #endregion

        #region Game Logic
        public void Move(int from, int to)
        {
            if (_moves < 1)
                throw new InvalidOperationException("Moves remaining 0. Can't move!");

            if (IsTimeFinished())
                throw new InvalidOperationException("Time finished. Can't move!");

            if (from == to && _moves > 0)
                throw new NotSupportedException("It's the same column!");

            if (IsValid(from, to) && _moves > 0)
                MoveDisk(from, to);
            else
                throw new NotSupportedException("Movement is not valid.");
        }

        private void MoveDisk(int from, int to)
        {
            Disk disk = (Disk)_towers[from].Pop();
            _towers[to].Push(disk);
            disk.Position = new System.Drawing.Point(to, _towers[to].Count - 1);
            _moves--;

            _undoFrom = from;
            _undoTo = to;
            _canUndo = !IsWon();

            if (_canUndo)
                _endTime = DateTime.Now;
        }

        private bool IsValid(int from, int to)
        {
            bool t = false;
            if (HasAny(from) && !HasAny(to))
                t = true;
            else if (HasAny(from) && HasAny(to) && IsLesser(from, to))
                t = true;
            return t;
        }

        /// <summary>
        /// Checks if the disk value from the tower "from" is lesser than the disk in the tower "to".
        /// </summary>
        /// <param name="from">Number of the tower.</param>
        /// <param name="to">Number of the tower.</param>
        private bool IsLesser(int from, int to)
        {
            bool t = false;

            Disk diskFrom = (Disk)_towers[from].Peek();
            Disk diskTo = (Disk)_towers[to].Peek();
            if (diskFrom.Value < diskTo.Value)
                t = true;

            return t;
        }

        public bool HasAny(int from)
        {
            bool t = true;
            object o = _towers[from].Peek();
            if (o == null)
                t = false;
            return t;
        }

        public bool IsFinished()
        {
            return _moves <= 0 || IsTimeFinished();
        }

        public bool IsWon()
        {
            if (this.TimeControl)
                return _towers[_end].Count == _size &&
                    (this.EndTime.Subtract(this.StartTime) < this.TimeAmount);
            else
                return _towers[_end].Count == _size;
        }

        public void Undo()
        {
            if (_canUndo)
            {
                MoveDisk(_undoTo, _undoFrom);
                _moves++;
                _canUndo = false;
                _undoFrom = _undoTo = -1;
            }
        }

        #endregion

        public new void ToString()
        {
            System.Diagnostics.Debug.WriteLine("--------------------------");
            System.Diagnostics.Debug.WriteLine(_towers[0].ToString());
            System.Diagnostics.Debug.WriteLine(_towers[1].ToString());
            System.Diagnostics.Debug.WriteLine(_towers[2].ToString());
            System.Diagnostics.Debug.WriteLine("--------------------------");
        }

        public bool IsTimeFinished()
        {
            TimeSpan elapsedTime = DateTime.Now.Subtract(this.StartTime);
            if (elapsedTime > this.TimeAmount && this.TimeControl)
                return true;
            else
                return false;
        }
    }
}
